use play_api::*;
use postcard::{from_bytes, to_allocvec};
use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition, TypeName, Value};
use serde::{Deserialize, Serialize};
const MC_PROFILES_TABLE: TableDefinition<'_, String, WrapAuthInfo> =
    TableDefinition::new("mc_profiles");
#[derive(Deserialize, Serialize, Clone, Debug)]
struct WrapAuthInfo(AuthInfo);
impl Value for WrapAuthInfo {
    type SelfType<'a> = WrapAuthInfo;

    type AsBytes<'a> = Vec<u8>;

    fn fixed_width() -> Option<usize> {
        None
    }

    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
    where
        Self: 'a,
    {
        from_bytes(data).unwrap()
    }

    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
    where
        Self: 'b,
    {
        to_allocvec(&value).unwrap()
    }

    fn type_name() -> redb::TypeName {
        TypeName::new("AuthInfoWrap")
    }
}
pub struct DB(Database);
impl AuthDB for DB {
    type Error = redb::Error;
    fn save_auth(&self, auth: AuthInfo) -> Result<(), Self::Error> {
        let db = &self.0;
        let guard = db.begin_write()?;
        guard
            .open_table(MC_PROFILES_TABLE)?
            .insert(auth.username.clone(), WrapAuthInfo(auth))?;
        guard.commit()?;
        Ok(())
    }
    fn get_auth(&self, name: &str) -> Result<Option<AuthInfo>, redb::Error> {
        let db = &self.0;
        let guard = db.begin_read()?;
        let table = guard.open_table(MC_PROFILES_TABLE)?;
        match table.get(name.to_owned())? {
            Some(v) => Ok(Some(v.value().0)),
            None => Ok(None),
        }
    }
    fn list_all_auth(&self) -> Result<Vec<play_api::AuthInfo>, redb::Error> {
        let db = &self.0;
        let guard = db.begin_read()?;
        let table = guard.open_table(MC_PROFILES_TABLE)?;
        let mut profiles: Vec<AuthInfo> = Vec::new();
        table.iter()?.for_each(|i| {
            if let Ok((_k, v)) = i {
                profiles.push(v.value().0)
            }
        });
        Ok(profiles)
    }

    fn remove_auth(&self, name: &str) -> Result<(), Self::Error> {
        let db = &self.0;
        let guard = db.begin_write()?;
        guard
            .open_table(MC_PROFILES_TABLE)?
            .remove(name.to_owned())?;
        guard.commit()?;
        Ok(())
    }
}
impl DB {
    pub fn new(path: &str) -> Self {
        let db = Database::create(path).unwrap();
        Self(db)
    }
    pub fn open(path: &str) -> Self {
        let db = Database::open(path).unwrap();
        Self(db)
    }
    pub fn init(&self) -> Result<(), redb::Error> {
        let guard = self.0.begin_write()?;
        guard.open_table(MC_PROFILES_TABLE)?;
        guard.open_table(CONFIG_TABLE)?;
        guard.commit()?;
        Ok(())
    }
}

const CONFIG_TABLE: TableDefinition<'_, &str, &[u8]> = TableDefinition::new("config");
const LAUNCHER_CONFIG: &str = "launcher_config";
impl ConfigDB for DB {
    type Error = redb::Error;
    fn get(&self) -> Result<Option<LauncherConfig>, Self::Error> {
        let guard = self.0.begin_read()?;
        let table = guard.open_table(CONFIG_TABLE)?;
        match table.get(LAUNCHER_CONFIG)? {
            Some(v) => {
                let config = postcard::from_bytes(v.value()).map_err(|e| {
                    redb::Error::Corrupted(format!("Corrupted launcher config: {}", e))
                })?;
                Ok(Some(config))
            }
            None => Ok(None),
        }
    }
    fn set(&self, config: &LauncherConfig) -> Result<(), Self::Error> {
        let bytes = postcard::to_allocvec(config)
            .map_err(|e| redb::Error::Corrupted(format!("postcard encode error: {}", e)))?;
        let guard = self.0.begin_write()?;
        {
            let mut table = guard.open_table(CONFIG_TABLE)?;
            table.insert(LAUNCHER_CONFIG, bytes.as_slice())?;
        }
        guard.commit()?;
        Ok(())
    }
}
